package com.client;

import com.crypto.BitcoinUtils;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.crypto.ECDSASign;
import com.crypto.Sha256;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Timestamp;
import com.google.protobuf.util.JsonFormat;
import com.protos.Peer.*;
import com.utils.certUtil;
import com.websocket.wsCallBack;
import com.websocket.wsCbClient;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;


public class RepChainClient extends Client{

    private String host;
    private String jksPath;
    private String password;
    private String alias;
    private ExecutorService executor = Executors.newCachedThreadPool();
//    private ExecutorService executor = Executors.newFixedThreadPool(10);


    public RepChainClient() {
    }

    /**
     *
     * @param host 服务主机地址
     */
    public RepChainClient(String host) {
        this.host = host;
    }

    /**
     *
     * @param host  服务主机地址
     * @param jksPath jks路径
     * @param password jks密码
     * @param alias 别名
     */
    public RepChainClient(String host, String jksPath, String password, String alias) {
        this.host = host;
        this.jksPath = jksPath;
        this.password = password;
        this.alias = alias;
    }

    /**
     * 创建交易的公共方法
     *
     * @param tranType 交易类型（invoke or deploy）
     * @param chainCodeIdPath
     * @param chaincodeInputFunc 要调用合约中的函数方法
     * @param param 方法参数
     * @param spcPackage  部署合约的时候用到，为合约文件的字符串
     * @param chaincodeId chaincodeId 合约ID
     * @param ctype  合约类型（JavaScript or scala）
     * @return 构建好的交易
     */
    public Transaction createTransaction(Transaction.Type tranType, String chainCodeIdPath,
                                               String chaincodeInputFunc, String param, String spcPackage, String chaincodeId,
                                               ChaincodeSpec.CodeType ctype) throws Exception{
        if (!ctype.equals(ChaincodeSpec.CodeType.CODE_JAVASCRIPT)) {
            ctype = ChaincodeSpec.CodeType.CODE_SCALA;
        } else {
            ctype = ChaincodeSpec.CodeType.CODE_JAVASCRIPT;
        }
        String name = chaincodeId;
        if (chaincodeId == "") {
            name = Sha256.hashstr(spcPackage);
        } else {
            if (chaincodeId.trim().equals("")) {
                Sha256.hashstr(spcPackage);
            } else {
                name = chaincodeId;
            }
        }
        long millis = System.currentTimeMillis() + 8*3600*1000;
        //deploy时取脚本内容hash作为 chaincodeId/name
        //invoke时调用者应该知道要调用的 chaincodeId
        ChaincodeID cid = ChaincodeID.newBuilder().setName(name).setPath(chainCodeIdPath).build();
        //构建运行代码
        ChaincodeInput cip = ChaincodeInput.newBuilder().setFunction(chaincodeInputFunc).addArgs(param).build();
        //初始化链码
        ChaincodeSpec chaincodeSpec = ChaincodeSpec.newBuilder().setChaincodeID(cid).setCtorMsg(cip).setTimeout(1000).
                setSecureContext("secureContext").setCodePackage(ByteString.copyFromUtf8(spcPackage)).setCtype(ctype).build();

        Transaction.Builder t = Transaction.newBuilder();
        t.setType(tranType).setChaincodeID(ByteString.copyFromUtf8(cid.toString()))
                .setPayload(chaincodeSpec).setMetadata(ByteString.copyFromUtf8("")).setTxid("")
                .setTimestamp(Timestamp.newBuilder().setSeconds(millis/1000).setNanos((int)(millis % 1000) * 1000000))
                .setConfidentialityLevel(ConfidentialityLevel.PUBLIC).setConfidentialityProtocolVersion( "confidentialityProtocolVersion-1.0")
                .setNonce(ByteString.copyFromUtf8("nonce")).setToValidators(ByteString.copyFromUtf8("toValidators"))
                .setCert(ByteString.EMPTY).setSignature(ByteString.EMPTY);
        String  txid = "";
        if (tranType.equals(Transaction.Type.CHAINCODE_DEPLOY)){
            txid = name;
        } else {
            txid = UUID.randomUUID().toString();
        }
        t = t.setTxid(txid);
        List list = certUtil.getCertFromJKS(new File(jksPath),password,alias);
        Certificate cert = (Certificate) list.get(0);
        t.setCert(ByteString.copyFromUtf8(BitcoinUtils.calculateBitcoinAddress(cert.getPublicKey().getEncoded())));
        byte[] sig = new ECDSASign().sign((PrivateKey) list.get(1),Sha256.hash(t.build().toByteArray()));
        t.setSignature(ByteString.copyFrom(sig));
        return t.build();
    }

    /**
     * 创建交易的公共方法
     * 暂时不用
     *
     * @param tranType  交易类型（invoke or deploy）
     * @param chainCodeIdPath
     * @param chaincodeInputFunc  要调用合约中的函数方法
     * @param params  List多参数
     * @param spcPackage 部署合约的时候用到，为合约文件的字符串
     * @param chaincodeId chaincodeId 合约ID
     * @param ctype  合约类型（JavaScript or scala）
     * @return  构建好的交易
     */
    public Transaction createTransaction(Transaction.Type tranType, String chainCodeIdPath,
                                         String chaincodeInputFunc, List params, String spcPackage, String chaincodeId,
                                         ChaincodeSpec.CodeType ctype) throws Exception{
        if (!ctype.equals(ChaincodeSpec.CodeType.CODE_JAVASCRIPT)) {
            ctype = ChaincodeSpec.CodeType.CODE_SCALA;
        } else {
            ctype = ChaincodeSpec.CodeType.CODE_JAVASCRIPT;
        }
        String name = chaincodeId;
        if (chaincodeId == "") {
            name = Sha256.hashstr(spcPackage);
        } else {
            if (chaincodeId.trim().equals("")) {
                Sha256.hashstr(spcPackage);
            } else {
                name = chaincodeId;
            }
        }
        long millis = System.currentTimeMillis() + 8*3600*1000;
        //deploy时取脚本内容hash作为 chaincodeId/name
        //invoke时调用者应该知道要调用的 chaincodeId
        ChaincodeID cid = ChaincodeID.newBuilder().setName(name).setPath(chainCodeIdPath).build();
        //构建运行代码
        ChaincodeInput cip = ChaincodeInput.newBuilder().setFunction(chaincodeInputFunc).addAllArgs(params).build();
        //初始化链码
        ChaincodeSpec chaincodeSpec = ChaincodeSpec.newBuilder().setChaincodeID(cid).setCtorMsg(cip).setTimeout(1000).
                setSecureContext("secureContext").setCodePackage(ByteString.copyFromUtf8(spcPackage)).setCtype(ctype).build();

        Transaction.Builder t = Transaction.newBuilder();
        t.setType(tranType).setChaincodeID(ByteString.copyFromUtf8(cid.toString()))
                .setPayload(chaincodeSpec).setMetadata(ByteString.copyFromUtf8("")).setTxid("")
                .setTimestamp(Timestamp.newBuilder().setSeconds(millis/1000).setNanos((int)(millis % 1000) * 1000000))
                .setConfidentialityLevel(ConfidentialityLevel.PUBLIC).setConfidentialityProtocolVersion( "confidentialityProtocolVersion-1.0")
                .setNonce(ByteString.copyFromUtf8("nonce")).setToValidators(ByteString.copyFromUtf8("toValidators"))
                .setCert(ByteString.EMPTY).setSignature(ByteString.EMPTY);
        String  txid = "";
        if (tranType.equals(Transaction.Type.CHAINCODE_DEPLOY)){
            txid = name;
        } else {
            txid = UUID.randomUUID().toString();
        }
        t = t.setTxid(txid);
        List list = certUtil.getCertFromJKS(new File(jksPath),password,alias);
        Certificate cert = (Certificate) list.get(0);
        t.setCert(ByteString.copyFromUtf8(BitcoinUtils.calculateBitcoinAddress(cert.getPublicKey().getEncoded())));
        byte[] sig = new ECDSASign().sign((PrivateKey) list.get(1),Sha256.hash(t.build().toByteArray()));
        t.setSignature(ByteString.copyFrom(sig));
        return t.build();
    }

    /**
     * 获取区块链信息
     * @return 当前区块链高度、总共交易数、currentBlockHash、previousBlockHash等信息
     */
    public JSONObject getChainInfo() {
        JSONObject jsonObject = doGet("http://" + host + "/chaininfo");
        return jsonObject;
    }

    /**
     * 获取区块高度
     * @return 当前链区块高度
     */
    public int getChainHeight() {
        JSONObject chainInfo = getChainInfo();
        int height = chainInfo.getJSONObject("result").getIntValue("height");
        return height;
    }

    /**
     * 获取当前块的hash值，Base64字符串形式
     * @return Base64字符串形式的hash值
     */
    public String getCurrentBlockHash() {
        JSONObject chainInfo = getChainInfo();
        String currentBlockHash = chainInfo.getJSONObject("result").getString("currentBlockHash");
        return currentBlockHash;
    }

    /**
     *
     * @param height 区块高度
     * @return Block
     */
    public Block getBlockByHeight(int height) {
        // 根据高度获取块数据
        JSONObject jsonObject = doGet("http://" + host + "/block/" + height);
        JSONObject result = jsonObject.getJSONObject("result");
        String json = result.toJSONString();
        Block.Builder builder = Block.newBuilder();
        try {
            JsonFormat.parser().merge(json,builder);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
        Block block = builder.build();
        return block;
    }

    /**
     *
     * @param BlockHash Block的hash值,Base64字符串
     * @return Block
     */
    public Block getBlockByBlockHash (String BlockHash) {
        JSONObject jsonObject = doGet("http://" + host + "/block/hash/"+ BlockHash);
        JSONObject result = jsonObject.getJSONObject("result");
        String json = result.toJSONString();
        Block.Builder builder = Block.newBuilder();
        try {
            JsonFormat.parser().merge(json,builder);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
        Block block = builder.build();
        return block;
    }

    /**
     *
     * @param BlockHash Block的hash值
     * @return Block
     */
    public Block getBlockByBlockHash (ByteString BlockHash) {
        String blockHash = Base64.encodeBase64String(BlockHash.toByteArray());
        Block block = getBlockByBlockHash(blockHash);
        return block;
    }

    /**
     * 根据txid查询交易
     * @param txid 交易txid
     * @return 返回检索到的交易
     */
    public Transaction getTransactionByTxid (String txid) {
        JSONObject jsonObject = doGet("http://" + host + "/transaction/"+ txid);
        JSONObject result = jsonObject.getJSONObject("result");
        String json = result.toJSONString();
        Transaction.Builder builder = Transaction.newBuilder();
        try {
            JsonFormat.parser().merge(json,builder);
        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
        Transaction transaction = builder.build();
        return transaction;
    }

    /**
     * 获取交易的function
     * @param tran 交易
     * @return
     */
    public String getFunction(Transaction tran) {
        String function = tran.getPayload().getCtorMsg().getFunction();
        return function;
    }

    /**
     * post交易到RepChain
     * @param json 提交的json数据
     * @return
     */
    public JSONObject postTranByString(String json) {
        String url = "http://" + host + "/transaction/postTranByString";
        JSONObject jsonObject = doPost(url,json);
        return jsonObject;
    }

    /**
     * 提交交易并回调提交交易的结果结果给callWs
     * @param callWs
     * @param json 提交的json数据
     * @param transaction  需要提交的交易
     */
    public void postTranByString(final wsCallBack callWs, final String json, final Transaction transaction) {
//        final wsCbClient[] server = {null};
        executor.submit(new Runnable() {
            wsCbClient server;
            Boolean connect;
            @Override
            public void run() {
                try {
                    server = new wsCbClient(new URI("ws://" + host + "/event"));
                    connect = server.connectBlocking(); // 阻塞，等待连接好
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(server.isOpen());
                    JSONObject result = postTranByString(json);
                    if (result.getString("err") == null) {
                        callWs.preDoTransaction(true, result);
                        if (connect)
                            server.feedBack(callWs, transaction);
                        else
                            callWs.verifyBlock(false, null, "无法确认是否出块");
                    } else {
                        callWs.preDoTransaction(false, result);
                        callWs.verifyBlock(false, null, "预执行失败，无法出块");
                        try {
                            server.getSocket().close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
    }

    /**
     * post交易到RepChain
     * @param url 请求路径
     * @param json 要提交的json数据
     * @return
     */
    public static JSONObject doPost(String url, String json){
        HttpClient client = HttpClientBuilder.create().build();
        HttpPost post = new HttpPost(url);
        JSONObject jsonObject = new JSONObject(true);
        try {
            StringEntity s = new StringEntity(json);
            s.setContentEncoding("UTF-8");
            s.setContentType("application/json");//发送json数据需要设置contentType
            post.setEntity(s);
            HttpResponse result = client.execute(post);
            int statusCode = result.getStatusLine().getStatusCode();  // 响应状态码
            String str = EntityUtils.toString(result.getEntity());
            jsonObject = (JSONObject) JSONObject.parse(str, Feature.OrderedField);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jsonObject;
    }

    /**
     * get请求区块链数据
     * @param url 请求的路径
     * @return
     */
    public static JSONObject doGet(String url){
        HttpClient client = HttpClientBuilder.create().build();
        HttpGet get = new HttpGet(url);
        JSONObject jsonObject = new JSONObject(true);
        try {
            HttpResponse result = client.execute(get);
            String str = EntityUtils.toString(result.getEntity());
            jsonObject = (JSONObject) JSONObject.parse(str, Feature.OrderedField);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return jsonObject;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public void setJksPath(String jksPath) {
        this.jksPath = jksPath;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public String getHost() {
        return host;
    }

    public String getJksPath() {
        return jksPath;
    }

    public String getPassword() {
        return password;
    }

    public String getAlias() {
        return alias;
    }
}
